package com.howie.hmvc.scanner;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javassist.ClassClassPath;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.LocalVariableAttribute;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.fileupload.FileItem;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;

import com.howie.hmvc.annotations.Param;
import com.howie.hmvc.annotations.Path;
import com.howie.hmvc.paramparse.AbsParamParse;
import com.howie.hmvc.paramparse.ParamBoolean;
import com.howie.hmvc.paramparse.ParamBooleans;
import com.howie.hmvc.paramparse.ParamByte;
import com.howie.hmvc.paramparse.ParamBytes;
import com.howie.hmvc.paramparse.ParamChart;
import com.howie.hmvc.paramparse.ParamCharts;
import com.howie.hmvc.paramparse.ParamDate;
import com.howie.hmvc.paramparse.ParamDouble;
import com.howie.hmvc.paramparse.ParamDoubles;
import com.howie.hmvc.paramparse.ParamFile;
import com.howie.hmvc.paramparse.ParamFiles;
import com.howie.hmvc.paramparse.ParamFloat;
import com.howie.hmvc.paramparse.ParamFloats;
import com.howie.hmvc.paramparse.ParamInteger;
import com.howie.hmvc.paramparse.ParamIntegers;
import com.howie.hmvc.paramparse.ParamLong;
import com.howie.hmvc.paramparse.ParamLongs;
import com.howie.hmvc.paramparse.ParamRequest;
import com.howie.hmvc.paramparse.ParamResponse;
import com.howie.hmvc.paramparse.ParamSession;
import com.howie.hmvc.paramparse.ParamShort;
import com.howie.hmvc.paramparse.ParamShorts;
import com.howie.hmvc.paramparse.ParamString;
import com.howie.hmvc.paramparse.ParamStrings;

/**
 * controller扫描器
 * @author dhz
 */
public class ControllerScanner extends ClassScanner{

	protected static Logger LOG = Logger.getLogger(ControllerScanner.class);
	private static Map<Class<?>,Class<?>> parseMap = new HashMap<Class<?>,Class<?>>();
	
	static{
		parseMap.put(HttpServletRequest.class,ParamRequest.class);
		parseMap.put(HttpServletResponse.class,ParamResponse.class);
		parseMap.put(HttpSession.class,ParamSession.class);
		parseMap.put(Date.class,ParamDate.class);
		parseMap.put(Date[].class,ParamDate.class);
		parseMap.put(Boolean.class,ParamBoolean.class);
		parseMap.put(Boolean[].class,ParamBooleans.class);
		parseMap.put(Byte.class,ParamByte.class);
		parseMap.put(Byte[].class,ParamBytes.class);
		parseMap.put(Character.class,ParamChart.class);
		parseMap.put(Character[].class,ParamCharts.class);
		parseMap.put(Double.class,ParamDouble.class);
		parseMap.put(Double[].class,ParamDoubles.class);
		parseMap.put(FileItem.class,ParamFile.class);
		parseMap.put(FileItem[].class,ParamFiles.class);
		parseMap.put(Float.class,ParamFloat.class);
		parseMap.put(Float[].class,ParamFloats.class);
		parseMap.put(Integer.class,ParamInteger.class);
		parseMap.put(Integer[].class,ParamIntegers.class);
		parseMap.put(Long.class,ParamLong.class);
		parseMap.put(Long[].class,ParamLongs.class);
		parseMap.put(Short.class,ParamShort.class);
		parseMap.put(Short[].class,ParamShorts.class);
		parseMap.put(String.class,ParamString.class);
		parseMap.put(String[].class,ParamStrings.class);
	}
	
	/**
	 * 从指定的包数组中扫描所有controller
	 * @param path
	 */
	public static Map<String,ActionInfo> scanControllersFromPackges(String[] packageNames)throws Exception{
		Map<String,ActionInfo> result = new HashMap<String,ActionInfo>();
		for(String packageName : packageNames){
			Map<String,ActionInfo> controllers = scanControllersFromPackge(packageName);
			Object key = isContains(result,controllers);
			if(key != null){
				throw new Exception("controller["+key.toString()+"]冲突");
			}
			result.putAll(controllers);
		}
		return result;
	}
	
	/**
	 * 从指定的包中扫描所有controller类及action方法
	 * @param path
	 */
	public static Map<String,ActionInfo> scanControllersFromPackge(String packageName)throws Exception{
		Map<String,ActionInfo> result = new HashMap<String,ActionInfo>();
		List<Class<?>> list = scannerPackge(packageName);
		int count = 0;
		for(Class<?> cls : list){
			//忽略非public类
			if(!Modifier.isPublic(cls.getModifiers())){
				continue;
			}
			//controller实例
			Object doInstance = cls.newInstance();
			//如未指定controller名称则用类名
			String value;
			Path controller = cls.getAnnotation(Path.class);
			if(controller != null){
				value = controller.value();
			}else{
				String name = cls.getSimpleName();
				String suffix = "Controller";
				if(name.endsWith(suffix)){
					name = name.substring(0,name.length() - suffix.length());
				}
				value = reName(name);
			}
			if(!value.startsWith("/")){
				value = "/" + value;
			}
			if(!value.endsWith("/")){
				value += "/";
			}
			//扫描controller
			Method[] ms = cls.getDeclaredMethods();
			for(Method m : ms){
				//忽略非public方法
				if(Modifier.isPublic(m.getModifiers())){
					ActionInfo actionInfo = new ActionInfo();
					actionInfo.setDoInstance(doInstance);
					String methodName = registerMethod(actionInfo,cls,m);
					String key = value+methodName;
					if(result.containsKey(key)){
						throw new Exception("controller["+value+"]["+methodName+"]冲突");
					}
					result.put(key,actionInfo);
				}
			}
			count++;
		}
		LOG.debug("共扫描到包["+packageName+"]中controller类：" + count + " 个");
		return result;
	}
	
	/*
	 * 添加方法
	 * @param cls
	 * @param methodName
	 * @param method
	 * @throws Exception
	 */
	private static String registerMethod(ActionInfo actionInfo,Class<?> cls,Method method) throws Exception{
		//如未指定action名称则用方法名
		String methodName;
		Path a = method.getAnnotation(Path.class);
		if(a != null){
			methodName = a.value();
			if(methodName.startsWith("/")){
				methodName = methodName.substring(1);
			}
		}else{
			methodName = reName(method.getName());
		}
		
		String[] paramNames = getParamNames(cls,method.getName());
		LOG.debug(cls.getName()+":"+methodName+":paramNames="+StringUtils.join(paramNames,','));
		Annotation[][] paramAns = method.getParameterAnnotations();
		Class<?>[] types = method.getParameterTypes();
		AbsParamParse[] paramParses = new AbsParamParse[types.length];
		for(int i=0;i<types.length;i++){
			//参数名,有Param注解则用Param注解的名称
			Param pm = getParamAnnotation(paramAns[i]);
			if(pm != null && !StringUtils.isBlank(pm.value())){
				paramNames[i] = pm.value();
			}
			//添加参数解析对象
			AbsParamParse paramParse = (AbsParamParse)parseMap.get(types[i]).newInstance();
			if(paramParse != null){
				paramParse.init(paramAns[i]);
				paramParse.setName(paramNames[i]);
			}
			paramParses[i] = paramParse;
		}
		actionInfo.setMethod(method);
		actionInfo.setParams(paramParses);
		return methodName;
	}
	
	/*
	 * 将驼峰命名规则的方法名改为用中划线隔开 如:getName => get-name
	 * @param name
	 * @return
	 */
	private static String reName(String name){
		Pattern p = Pattern.compile("[A-Z]");
		Matcher m = p.matcher(name);
		StringBuffer sb = new StringBuffer();
		while(m.find()){
			m.appendReplacement(sb,"-" + m.group().toLowerCase());
		}
		m.appendTail(sb);
		if(sb.charAt(0) == '-'){
			sb.deleteCharAt(0);
		}
		return sb.toString();
	}
	
	/*
	 * 判断第一个map中是否包含第二个map中的任意一个key
	 * @param src
	 * @param sub
	 * @return 返回存在的key，不存在则返回null
	 */
	private static Object isContains(Map<String,?> src,Map<String,?> sub){
		for(Object key : sub.keySet()){
			if(src.containsKey(key)){
				return key;
			}
		}
		return null;
	}

	/*
	 * 获取方法参数名
	 * @param clz
	 * @param methodName
	 * @return
	 * @throws Exception
	 */
	private static String[] getParamNames(Class<?> clz,String methodName) throws Exception{
		ClassPool pool = ClassPool.getDefault();
		pool.insertClassPath(new ClassClassPath(clz));
		CtClass ctClass = pool.get(clz.getName());
		CtMethod cm = ctClass.getDeclaredMethod(methodName);
		CodeAttribute codeAttribute = cm.getMethodInfo().getCodeAttribute();
		LocalVariableAttribute attr = (LocalVariableAttribute)codeAttribute.getAttribute(LocalVariableAttribute.tag);
		if(attr == null){
			throw new Exception("class文件中没有LocalVariableAttribute，无法获取参数名");
		}
		String[] variableNames = new String[cm.getParameterTypes().length];
		int staticIndex = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;
		for (int i = 0; i < variableNames.length; i++){
			variableNames[i] = attr.variableName(i + staticIndex);
		}
		return variableNames;
	}
	
	/*
	 * 获取Param注解
	 * @param an
	 * @return
	 */
	private static Param getParamAnnotation(Annotation[] an){
		for(Annotation a : an){
			if(a instanceof Param){
				return (Param)a;
			}
		}
		return null;
	}
	
}
